package com.weblog.user.service;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.afs.model.v20180112.AuthenticateSigRequest;
import com.aliyuncs.afs.model.v20180112.AuthenticateSigResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.weblog.model.user.Role;
import com.weblog.model.user.User;
import com.weblog.model.user.vo.FansInfoVo;
import com.weblog.user.dao.AdminDao;
import com.weblog.user.dao.RoleDao;
import com.weblog.user.dao.UserDao;
import com.weblog.user.entity.UserDetailVo;
import com.weblog.user.entity.UserInfoDTO;
import com.weblog.user.utils.AccessUtil;
import constants.RedisKey;
import enums.ExceptionEnum;
import exception.MyException;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import util.DateUtil;
import util.IdWorker;
import util.MapSizeUtil;

import javax.persistence.criteria.*;
import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author MaoLin Wang
 * @date 2019/9/1817:51
 */
@Slf4j
@Service
public class UserService {

    public static String ACTIVE_KEY = "active:" + DateUtil.getTodayStr();

    public static final String ONLINE_KEY = "blog:online";

    /**
     * 用于记录每周登录的用户id和其在线名次的映射（可改成redis存储）
     * key:yyyyMMdd:One/Two
     */
    public static Map<String, Long> uidMap = new ConcurrentHashMap<>(20);

    /**
     * 每周一开始，每在线一个，count+1
     */
    private static AtomicLong count = new AtomicLong(1);



    /**
     * 发送验证码
     *
     * @param mobile
     */
    public void sendSms(String mobile) {
        //生成六位随机数字
        String code = RandomStringUtils.randomNumeric(6);
        //缓存中放一份
        jsonRedisTemplate.opsForValue().set("weblog_checkCode_" + mobile, code, 60, TimeUnit.SECONDS);
        Map<String, String> map = new HashMap<>(mapSizeUtil.initCapacity(2));
        map.put("mobile", mobile);
        map.put("checkcode", code);
        //给用户发一份(routingKey:队列名)
        rabbitTemplate.convertAndSend("weblogSms", map);

        //在控制台显示一份
        System.out.println("验证码为" + code);
    }

    /**
     * 校验手机号是否已被注册
     *
     * @param mobile
     * @return
     */
    public int checkMobile(String mobile) {
        User user = userDao.findUserByMobile(mobile);
        if (user == null) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * 注册
     *
     * @param user
     */
    @Transactional(rollbackFor = Exception.class)
    public int add(User user) {
        User loginname = userDao.findAllByLoginname(user.getLoginname());
        if (loginname == null) {

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                user.setId(idWorker.nextId() + "");
                user.setPassword(encoder.encode(user.getPassword()));
                user.setIsblack("0");
                String date = sdf.format(new Date());
                user.setRegisdate(sdf.parse(date));
                user.setLastlogin(sdf.parse(date));
                user.setAvatar("");
                user.setScore(0);
                user.setFansnum(0);
                user.setFollownum(0);
                user.setOnlinetime(0L);
                userDao.save(user);
                return 1;
            } catch (ParseException e) {
                e.printStackTrace();
                return 0;
            }
        } else {
            //登录名已存在
            return 2;
        }
    }


    /**
     * 人机检测
     *
     * @param map
     */
    public int checkSecurity(Map<String, String> map) throws ClientException {

        try {
            IAcsClient client = null;
            IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI4FcrbSnhUxEDajedTxqp", "8jHbiaTkwQLe7Wq9yOZS5iPnZZ6HaD");
            client = new DefaultAcsClient(profile);

            DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", "afs", "afs.aliyuncs.com");

            AuthenticateSigRequest request = new AuthenticateSigRequest();
            request.setSessionId(map.get("sessionId"));// 必填参数，从前端获取，不可更改，android和ios只传这个参数即可
            request.setSig(map.get("sig"));// 必填参数，从前端获取，不可更改
            request.setToken(map.get("stoken"));// 必填参数，从前端获取，不可更改
            request.setScene(map.get("scene"));// 必填参数，从前端获取，不可更改
            request.setAppKey("FFFF0N00000000008555");// 必填参数，后端填写
            request.setRemoteIp("http://localhost:10000");// 必填参数，后端填写
            AuthenticateSigResponse response = client.getAcsResponse(request);
            if (response.getCode() == 100) {
                return 1;
            } else {
                System.out.println(response.getMsg());
                return 0;
            }
            // TODO
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 账号登录
     *
     * @param loginName
     * @param password
     * @return
     */
    public UserInfoDTO login(String loginName, String password) {
        User login_user = userDao.findByLoginname(loginName);

        if (login_user != null && encoder.matches(password, login_user.getPassword())) {

            UserInfoDTO dto=new UserInfoDTO();
            dto.setUser(login_user);
            Role role = roleDao.findRoleByRname("role_user_common");
            List<Role> roleList = new ArrayList<>();
            roleList.add(role);
            dto.setRoleList(roleList);
            setOnlineMark2(login_user.getId());

            return dto;
        }
        return null;
    }

    /**
     * 在线统计-使用concurrentHashMap
     * 使用redis的位图
     * key：active +当前日期戳 如active:20200807 改日活跃人数 online：在线人数
     * offset:登录用户od
     * value：true
     *
     * @param userId
     */
    private void setOnlineMark(String userId) {
        if (userId != null) {
            if (!uidMap.containsKey(userId)) {
                //如果使用put，则可能在put之前另一个线程已经put完了,即一个线程在put之前，另一个线程可能发现不存在该key就已经进入if
                uidMap.putIfAbsent(userId, count.getAndIncrement());
            }
            Long userRank = uidMap.get(userId);
            byteRedisTemplate.opsForValue().setBit(ACTIVE_KEY, userRank, true);
            byteRedisTemplate.opsForValue().setBit(ONLINE_KEY, userRank, true);
        }
    }
    /**
     * 在线统计-使用redis
     * 使用redis的位图
     * key：active +当前日期戳 如active:20200807 改日活跃人数 online：在线人数
     * offset:登录用户od
     * value：true
     *
     * @param userId
     */
    private void setOnlineMark2(String userId) {
        if (userId != null) {
            Integer rank = (Integer) jsonRedisTemplate.opsForHash().get(RedisKey.ONLINE_RANK, userId);
            if (rank==null){
                rank= Math.toIntExact(jsonRedisTemplate.opsForValue().increment(RedisKey.ONLINE_COUNT));
                jsonRedisTemplate.opsForHash().put(RedisKey.ONLINE_RANK,userId,rank);
            }
            byteRedisTemplate.opsForValue().setBit(ACTIVE_KEY, rank, true);
            byteRedisTemplate.opsForValue().setBit(ONLINE_KEY, rank, true);
        }
    }

    /**
     * 根据id查询用户 (登录)
     *
     * @param id
     * @return
     */
    public User getUsernameById(String id) {
        try {


            User user = userDao.findById(id).get();
            if (user == null) {
                return null;
            }
            return user;
        } catch (Exception e) {
            throw new MyException(ExceptionEnum.USER_NOT_FOUND);
        }
    }


    /**
     * 校验原密码
     *
     * @param loginname
     * @param oldPwd
     * @return
     */
    public int checkPwd(String loginname, String oldPwd) {

        User rowUser = userDao.findByLoginname(loginname);
        if (rowUser != null && encoder.matches(oldPwd, rowUser.getPassword())) {
            //校验通过
            return 1;
        }
        return 0;
    }

    /**
     * 修改密码
     *
     * @param loginname
     * @param newPwd
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public void updatePwd(String loginname, String newPwd) {
        try {
            userDao.updatePwd(loginname, encoder.encode(newPwd));

        } catch (MyException e) {

            throw new MyException(ExceptionEnum.UPDATE_ERROR);
        }
    }

    /**
     * 条件查询+分页
     *
     * @param searchMap
     * @param page
     * @param size
     * @return
     */
    public Page<User> PageBySearch(Map<String, Object> searchMap, int page, int size) {
        Specification<User> specification = createSpecification(searchMap);
        Pageable pageable = PageRequest.of(page - 1, size);
        return userDao.findAll(specification, pageable);
    }

    /**
     * 拉黑
     *
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    public int blackUser(String id) {
        String token = (String) request.getAttribute("role_superadmin");
        if (token == null || "".equals(token)) {
            //普通管理员
            int flag = accessUtil.isAccess("role_user", "user_update");
            if (flag == 2) {
                return 2;
            } else {
                int result = userDao.blackUserById(id);
                if (result > 0) {
                    //删除成功
                    return 1;
                } else {
                    //删除失败
                    return 0;
                }
            }

        }
        //超级管理员
        int flag = userDao.blackUserById(id);
        if (flag > 0) {
            //删除成功
            return 1;
        } else {
            //删除失败
            return 0;
        }

    }


    /**
     * 根据ID查询用户信息
     *
     * @param id
     * @return
     */
    public User findById(String id) {
        if (id != null) {
            return userDao.findById(id).get();
        }
        return null;
    }

    /**
     * 后台修改
     *
     * @param user
     */
    @Transactional(rollbackFor = Exception.class)
    public int MsUpdate(User user) {

        String token = (String) request.getAttribute("claims_admin");
        if (token == null || "".equals(token)) {
            //超级管理员或普通用户修改个人资料
            userDao.save(user);
            return 1;

        } else {
            //普通管理员
            int flag = accessUtil.isAccess("role_user", "user_update");
            if (flag == 2) {
                return 2;
            }
            userDao.save(user);
            return 1;
        }


    }

    /**
     * 前台修改个人资料
     *
     * @param user
     */
    @Transactional(rollbackFor = Exception.class)
    public int WebUpdate(User user) {
        //超级管理员或普通用户修改个人资料
        userDao.updateInfo(user.getUsername(), user.getGender(), user.getMobile(), user.getBirthday(), user.getEmail(), user.getId());

        return 1;

    }

    /**
     * 查询全部列表
     *
     * @return
     */
    public List<User> findAll() {
        return userDao.findAll();
    }

    /**
     * 构建构建查询
     *
     * @param searchMap
     * @return
     */
    private Specification<User> createSpecification(Map<String, Object> searchMap) {
        log.info("搜索参数:{}", searchMap);


        return new Specification<User>() {

            /**
             * @param root  根对象，也就是要把条件封装到哪个对象中，where leiming=user.getId
             * @param query 封装的是查询关键字， 比如group by order by等
             * @param cb    用来封装条件对象
             * @return
             */
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> list = new ArrayList<Predicate>();

                // 手机号
                if (searchMap.get("mobile") != null && !"".equals(searchMap.get("mobile"))) {
                    Expression<String> mobile = root.get("mobile").as(String.class);
                    Predicate predicate = cb.like(mobile, "%" + (String) searchMap.get("mobile") + "%");
                    list.add(predicate);
                }
                // 性别 0女 1男
                if (searchMap.get("gender") != null && !"".equals(searchMap.get("gender"))) {
                    list.add(cb.equal(root.get("gender").as(String.class), (String) searchMap.get("gender")));
                }

                // 用户昵称
                if (searchMap.get("username") != null && !"".equals(searchMap.get("username"))) {
                    list.add(cb.like(root.get("username").as(String.class), "%" + (String) searchMap.get("username") + "%"));
                }
                // 邮箱
                if (searchMap.get("email") != null && !"".equals(searchMap.get("email"))) {
                    list.add(cb.like(root.get("email").as(String.class), "%" + (String) searchMap.get("email") + "%"));
                }

                // 黑名单状态 1是 0否
                if (searchMap.get("isblack") != null && !"".equals(searchMap.get("isblack"))) {
                    Expression<String> isblack = root.get("isblack").as(String.class);
                    String search_isblack = (String) searchMap.get("isblack");
                    //约定 0代表查询所有用户，忽略黑名单状态
                    if ("1".equals(search_isblack)) {
                        //当仅当状态为1时添加条件正常用户
                        Predicate predicate = cb.equal(isblack, "0");
                        list.add(predicate);
                    }
                }
                if (searchMap.get("regisdate") != null && !"".equals(searchMap.get("regisdate"))) {
                    Expression<Date> regisdate = root.get("regisdate").as(Date.class);
                    List<String> regis_range = (List<String>) searchMap.get("regisdate");
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    Date from = null;//开始日期
                    Date to = null;//结束日期
                    try {
                        from = sdf.parse(regis_range.get(0));
                        to = sdf.parse(regis_range.get(1));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                    Predicate between = cb.between(regisdate, from, to);
                    list.add(between);
                }
                if (searchMap.get("currentUserId") != null && !"".equals(searchMap.get("currentUserId"))) {
                    //排除掉当前用户信息
                    list.add(cb.notEqual(root.get("id").as(String.class), (String) searchMap.get("currentUserId")));
                }

                //new一个Predicaet数组存放最终返回值的条件
                Predicate[] predicates = new Predicate[list.size()];
                //list转成数组
                predicates = list.toArray(predicates);
                return cb.and(predicates);
            }
        };

    }


    /**
     * 根据id查询用户名
     *
     * @param id
     * @return
     */
    public String findUserNameById(String id) {
        try {
            return userDao.findUsernameById(id);
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 根据id查询用户名和头像地址
     *
     * @param id
     * @return
     */
    public String findUserNameAndAvatarById(String id) {
        try {
            return userDao.findUserNameAndAvatarById(id);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 查询总记录数
     *
     * @return
     */
    public long count() {
        return userDao.count();
    }

    /**
     * 上传/修改头像
     *
     * @param url
     */
    @Transactional(rollbackFor = Exception.class)
    public void uploadAvatar(String url) {
        String userid = (String) request.getAttribute("id");
        userDao.updateAvatar(userid, url);
    }

    /**
     * 根据id查找头像
     *
     * @return
     */
    public String findAvatarById(String id) {
        return userDao.findAvatarById(id);
    }

    /**
     * 增加积分
     *
     * @param id
     */
    @Transactional(rollbackFor = Exception.class)
    public void incrScore(String id, int score) {
        userDao.incrScore(id, score);
    }



    /**
     * 查询个人资料
     *
     * @param id
     * @return
     */
    public Map<String, Object> userInfoDetail(String id) {
        return userDao.userInfoDetail(id);
    }

    /**
     * 查询粉丝信息
     *
     * @param ids
     * @return
     */
    public List<FansInfoVo> fansList(List<String> ids, List<String> iseach) {
        var result = new ArrayList<FansInfoVo>();
        FansInfoVo vo=null;
        for (int i = 0; i < ids.size(); i++) {
            vo=new FansInfoVo();

            vo.setAvatar(userDao.findAvatarById(ids.get(i)));
            vo.setId(ids.get(i));
            vo.setUsername(userDao.findUsernameById(ids.get(i)));
            vo.setIseach(iseach.get(i));
            result.add(vo);

        }

        return result;
    }

    /**
     * 查询关注用户信息
     *
     * @param ids
     * @return
     */
    public List<Map<String, String>> focusList(List<String> ids) {
        var result = new ArrayList<Map<String, String>>();
        Map<String, String> map;
        for (String id : ids) {
            map = new HashMap<>(mapSizeUtil.initCapacity(3));
            map.put("id", id);
            map.put("avatar", userDao.findAvatarById(id));
            map.put("username", userDao.findUsernameById(id));
            result.add(map);
        }
        return result;
    }

    /**
     * 获取当日活跃人数
     *
     * @return
     */
    public Integer getActive() {
        return getActiveOnlineCount(ACTIVE_KEY);
    }


    /**
     * 获取当前在线人数
     *
     * @return
     */
    public int getOnline() {
        return getActiveOnlineCount(ONLINE_KEY);
    }

    private Integer getActiveOnlineCount(String key) {
        byte[] bytes = (byte[]) byteRedisTemplate.opsForValue().get(key);
        if (bytes != null) {
            BitSet bitSet = BitSet.valueOf(bytes);
            return bitSet.cardinality();
        }
        return null;
    }

    /**
     * TODO 从当前在线的bit中移除退出用户
     */
    public void logout() {
    }


    /**
     * 统计7日连续在线
     *
     * @param userId 要判断的用户id
     * @return 当前用户是否在本周连续7日在线
     */
    public boolean countSevenActive(Long userId) {
        List<String> dayStrList = DateUtil.getDayStrList();

        byte[][] mergerdBytes = new byte[dayStrList.size()][];
        for (int i = 0; i < dayStrList.size(); i++) {
            mergerdBytes[i] = dayStrList.get(i).getBytes();
        }
        final String key = String.format(RedisKey.sevenResult, DateUtil.getTodayStr());
        if (!byteRedisTemplate.hasKey(key)) {
            byteRedisTemplate.execute(((RedisCallback<Long>) con -> con.bitOp(RedisStringCommands.BitOperation.AND, key.getBytes(), mergerdBytes)));
            //第二天零点过期
            byteRedisTemplate.expireAt(key, DateUtil.getTomorrow());
        }

        Boolean bit = byteRedisTemplate.opsForValue().getBit(key, (Integer) jsonRedisTemplate.opsForHash().get(RedisKey.ONLINE_RANK,userId));
        return bit == null ? false : bit;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateScore(String id, int score) {
        userDao.updateScore(id, score);
    }



    @Autowired
    private UserDao userDao;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private AdminDao adminDao;

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Autowired
    private AccessUtil accessUtil;

    @Autowired
    private RedisTemplate jsonRedisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MapSizeUtil mapSizeUtil;

    @Autowired
    private RedisTemplate byteRedisTemplate;
}




